using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;
using Microsoft.CSharp;

namespace LinqToExcel
{
    public delegate Assembly ResolveAssemblyHandler(ResolveEventArgs args);

    public class LinqToExcelEntityBuilder<T> : IBuilder<string, T> where T: class 
    {
        protected readonly ResolveAssemblyHandler myAssemblyResolver;

        public LinqToExcelEntityBuilder(ResolveAssemblyHandler assemblyResolver)
        {
            AppDomain.CurrentDomain.AssemblyResolve += (CurrentDomainAssemblyResolve);

            myAssemblyResolver = assemblyResolver;
        }

        public T Build(string worksheetName)
        {
            var targetUnit = new CodeCompileUnit();
            var cdNamespace = new CodeNamespace("LinqToExcel.Entities");
            cdNamespace.Imports.Add(new CodeNamespaceImport("LinqToExcel"));

            // invalid chars detection?
            var className = string.Format("{0}LinqToExcelEntity", worksheetName);

            var targetClass = new CodeTypeDeclaration(className)
                                  {
                                      TypeAttributes = TypeAttributes.Public,
                                      IsClass = true                                      
                                  };
            targetClass.BaseTypes.Add(new CodeTypeReference(typeof(T)));

            var linqToExcelAttrib = new CodeAttributeDeclaration("ExcelSheet");
            linqToExcelAttrib.Arguments.Add(new CodeAttributeArgument
                                                {
                                                    Name = "Name",
                                                    Value = new CodePrimitiveExpression(worksheetName)
                                                });
            targetClass.CustomAttributes.Add(linqToExcelAttrib);

            cdNamespace.Types.Add(targetClass);
            targetUnit.Namespaces.Add(cdNamespace);

            var entityProxyType = GenerateCode(targetUnit);
            return (T)Activator.CreateInstance(entityProxyType);
        }

        private static Type GenerateCode(CodeCompileUnit targetUnit)
        {
            var codeProvider = new CSharpCodeProvider();

            var parameters = new CompilerParameters();
            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add(typeof(T).Assembly.Location);
            parameters.ReferencedAssemblies.Add(typeof(ExcelProvider).Assembly.Location);
            parameters.GenerateInMemory = true;
            parameters.GenerateExecutable = false;

            var results = codeProvider.CompileAssemblyFromDom(parameters, targetUnit);
            if (results.Errors.HasErrors)
            {
                var errorMessage = results.Errors.Count + " Errors:";
                for (var x = 0; x < results.Errors.Count; x++)
                {
                    errorMessage = errorMessage + "\r\nLine: " +
                                   results.Errors[x].Line + " - " + results.Errors[x].ErrorText;
                }
                
                throw new InvalidOperationException(errorMessage);
            }

            return results.CompiledAssembly.GetTypes()[0];
        }

        Assembly CurrentDomainAssemblyResolve(object sender, ResolveEventArgs args)
        {
            if (args.Name.StartsWith("LinqToExcel"))
                return typeof (ExcelProvider).Assembly;

            var resolvedAssembly = myAssemblyResolver.Invoke(args);

            if (resolvedAssembly != null)
                return resolvedAssembly;

            throw new NotSupportedException(string.Format("Unhandled assembly resolution request: {0}",
                                                          args.Name));
        }
    }
}